program MeshViewer;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  SampleCommon,
  DIrr in '..\..\DSource\DIrr.pas',
  DIrr.GUI in '..\..\DSource\DIrr.GUI.pas',
  DIrr.Color in '..\..\DSource\DIrr.Color.pas',
  DIrr.Rect in '..\..\DSource\DIrr.Rect.pas',
  DIrr.Event in '..\..\DSource\DIrr.Event.pas',
  DIrr.Vector2D in '..\..\DSource\DIrr.Vector2D.pas',
  DIrr.Vector3D in '..\..\DSource\DIrr.Vector3D.pas',
  DIrr.Video in '..\..\DSource\DIrr.Video.pas',
  DIrr.Base in '..\..\DSource\DIrr.Base.pas',
  DIrr.Types in '..\..\DSource\DIrr.Types.pas',
  DIrr.Scene in '..\..\DSource\DIrr.Scene.pas',
  DIrr.Structs in '..\..\DSource\DIrr.Structs.pas',
  DIrr.Aabbox3D in '..\..\DSource\DIrr.Aabbox3D.pas',
  DIrr.Matrix4 in '..\..\DSource\DIrr.Matrix4.pas',
  DIrr.Texture in '..\..\DSource\DIrr.Texture.pas',
  DIrr.Collections in '..\..\DSource\DIrr.Collections.pas',
  DIrr.Consts in '..\..\DSource\DIrr.Consts.pas',
  DIrr.IO in '..\..\DSource\DIrr.IO.pas',
  DIrr.Quaternion in '..\..\DSource\DIrr.Quaternion.pas';

const
  // Values used to identify individual GUI elements
  GUI_ID_BASE                           =     $10000;
  GUI_ID_DIALOG_ROOT_WINDOW             =     GUI_ID_BASE + 0;

  GUI_ID_X_SCALE                        =     GUI_ID_BASE + 1;
  GUI_ID_Y_SCALE                        =     GUI_ID_BASE + 2;
  GUI_ID_Z_SCALE                        =     GUI_ID_BASE + 3;

  GUI_ID_OPEN_MODEL                     =     GUI_ID_BASE + 4;
  GUI_ID_SET_MODEL_ARCHIVE              =     GUI_ID_BASE + 5;
  GUI_ID_LOAD_AS_OCTREE                 =     GUI_ID_BASE + 6;

  GUI_ID_SKY_BOX_VISIBLE                =     GUI_ID_BASE + 7;
  GUI_ID_TOGGLE_DEBUG_INFO              =     GUI_ID_BASE + 8;

  GUI_ID_DEBUG_OFF                      =     GUI_ID_BASE + 9;
  GUI_ID_DEBUG_BOUNDING_BOX             =     GUI_ID_BASE + 10;
  GUI_ID_DEBUG_NORMALS                  =     GUI_ID_BASE + 11;
  GUI_ID_DEBUG_SKELETON                 =     GUI_ID_BASE + 12;
  GUI_ID_DEBUG_WIRE_OVERLAY             =     GUI_ID_BASE + 13;
  GUI_ID_DEBUG_HALF_TRANSPARENT         =     GUI_ID_BASE + 14;
  GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES   =     GUI_ID_BASE + 15;
  GUI_ID_DEBUG_ALL                      =     GUI_ID_BASE + 16;

  GUI_ID_MODEL_MATERIAL_SOLID           =     GUI_ID_BASE + 17;
  GUI_ID_MODEL_MATERIAL_TRANSPARENT     =     GUI_ID_BASE + 18;
  GUI_ID_MODEL_MATERIAL_REFLECTION      =     GUI_ID_BASE + 19;

  GUI_ID_CAMERA_MAYA                    =     GUI_ID_BASE + 20;
  GUI_ID_CAMERA_FIRST_PERSON            =     GUI_ID_BASE + 21;

  GUI_ID_POSITION_TEXT                  =     GUI_ID_BASE + 22;

  GUI_ID_ABOUT                          =     GUI_ID_BASE + 23;
  GUI_ID_QUIT                           =     GUI_ID_BASE + 24;

  GUI_ID_TEXTUREFILTER                  =     GUI_ID_BASE + 25;
  GUI_ID_SKIN_TRANSPARENCY              =     GUI_ID_BASE + 26;
  GUI_ID_SKIN_ANIMATION_FPS             =     GUI_ID_BASE + 27;

  GUI_ID_BUTTON_SET_SCALE               =     GUI_ID_BASE + 28;
  GUI_ID_BUTTON_SCALE_MUL10             =     GUI_ID_BASE + 29;
  GUI_ID_BUTTON_SCALE_DIV10             =     GUI_ID_BASE + 30;
  GUI_ID_BUTTON_OPEN_MODEL              =     GUI_ID_BASE + 31;
  GUI_ID_BUTTON_SHOW_ABOUT              =     GUI_ID_BASE + 32;
  GUI_ID_BUTTON_SHOW_TOOLBOX            =     GUI_ID_BASE + 33;
  GUI_ID_BUTTON_SELECT_ARCHIVE          =     GUI_ID_BASE + 34;

  GUI_ID_ANIMATION_INFO                 =     GUI_ID_BASE + 35;

const
  MAX_FRAMERATE       =     80;
  DEFAULT_FRAMERATE   =     30;

type
  TMyEventReceiver = class(TEventReceiver)
  private
    function OnKeyUp(KeyCode: EKEY_CODE): Boolean;
  public
    function OnEvent(const Event: SEvent): Boolean; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

function SingleToStr(const Value: Extended): String; inline;
var
  Buffer: array[0..63] of Char;
begin
  SetString(Result, Buffer, FloatToText(Buffer, Value, fvExtended,
    ffGeneral, 7, 0, FormatSettings));
end;

var
  DriverType: E_DRIVER_TYPE;
  Device: TIrrlichtDevice;
  Driver: TVideoDriver;
  Env: TGUIEnvironment;
  Receiver: TMyEventReceiver;
  Smgr: TSceneManager;
  Attributes: TAttributes;
  xml: TXMLReader;
  skin: TGUISkin;
  font: TGUIFont;
  Menu, SubMenu: TGUIContextMenu;
  Bar: TGUIToolBar;
  Image: TTexture;
  Box: TGUIComboBox;
  Model: TSceneNode;
  FPSText, PosText: TGUIStaticText;
  m: TAnimatedMesh;
  Octree: Boolean = False;
  UseLight: Boolean = False;
  AnimModel: TAnimatedMeshSceneNode;
  SkyBox: TSceneNode;
  Camera: array[0..1] of TCameraSceneNode = (nil, nil);
  Img: TGUIImage;

  CamPosition: TVector3DF;
  focused, hasFocus: Boolean;
  str: String;
  Cam: TCameraSceneNode;
  NameStr: PAnsiChar;
  NodeName: PWideChar;
  StartUpModelFile: String = '';
  MessageText: String;
  Caption: String = '';
  i: EGUI_DEFAULT_COLOR;
  Col: TIrrColor;
  DataList: TDataList;

(*
Function hasModalDialog() checks if we currently have a modal dialog open.
*)
function HasModalDialog: Boolean;
var
  lEnv: TGUIEnvironment;
  lFocused: TGUIElement;
begin
  if not Assigned(Device) then
    Exit(False);

  lEnv := Device.GetGUIEnvironment;
  lFocused := lEnv.GetFocus;

  while Assigned(lFocused) do
  begin
    if lFocused.IsVisible and lFocused.HasType(EGUIET_MODAL_SCREEN) then
      Exit(True);

    lFocused := lFocused.Parent;
  end;

  Result := False;
end;

(*
  Handle "menu item clicked" events.
*)
procedure RevertDebugDataElement(AModel: TSceneNode; Element: E_DEBUG_SCENE_TYPE); inline;
var
  DebugSets: E_DEBUG_SCENE_TYPES;
begin
  if Assigned(AModel) then
  begin
    DebugSets := AModel.DebugData;

    if Element in DebugSets then
      Exclude(DebugSets, Element)
    else
      Include(DebugSets, Element);

    AModel.DebugData := DebugSets;
  end;
end;

(*
Function showAboutText() displays a messagebox with a caption and
a message text. The texts will be stored in the MessageText and Caption
variables at startup.
*)
procedure ShowAboutText;
begin
	// create modal message box with the text
	// loaded from the xml file.
  Device.GetGUIEnvironment.AddMessageBox(PWideChar(Caption), PWideChar(MessageText));
end;

(*
Toggle between various cameras
*)
procedure SetActiveCamera(NewActive: TCameraSceneNode);
var
  Current: TCameraSceneNode;
begin
  if not Assigned(Device) then
    Exit;

  Current := Device.GetSceneManager.GetActiveCamera;
  Current.SetInputReceiverEnabled(False);

  NewActive.SetInputReceiverEnabled(True);
  Device.GetSceneManager.SetActiveCamera(NewActive);
end;

procedure OnMenuItemSelected(Menu: TGUIContextMenu);
var
  id: S32;
  lenv: TGUIEnvironment;
begin
  id := Menu.GetItemCommandID(Menu.GetSelectedItem);
  lenv := Device.GetGUIEnvironment;

  case id of
    GUI_ID_OPEN_MODEL:  // FilOnButtonSetScalinge -> Open Model
      lenv.AddFileOpenDialog('Please select a model file to open');
    GUI_ID_SET_MODEL_ARCHIVE: // File -> Set Model Archive
      lenv.AddFileOpenDialog('Please select your game archive/directory');
    GUI_ID_LOAD_AS_OCTREE: // File -> LoadAsOctree
    begin
      Octree := not Octree;
      Menu.SetItemChecked(Menu.GetSelectedItem, Octree);
    end;
    GUI_ID_QUIT: // File -> Quit
      Device.CloseDevice;
    GUI_ID_SKY_BOX_VISIBLE: // View -> Skybox
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      SkyBox.SetVisible(not SkyBox.IsVisible);
    end;
    GUI_ID_DEBUG_OFF: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem + 1, False);
      Menu.SetItemChecked(Menu.GetSelectedItem + 2, False);
      Menu.SetItemChecked(Menu.GetSelectedItem + 3, False);
      Menu.SetItemChecked(Menu.GetSelectedItem + 4, False);
      Menu.SetItemChecked(Menu.GetSelectedItem + 5, False);
      Menu.SetItemChecked(Menu.GetSelectedItem + 6, False);

      if Assigned(Model) then
        Model.SetDebugDataVisible(EDS_OFF);
    end;
    GUI_ID_DEBUG_BOUNDING_BOX:   // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      RevertDebugDataElement(Model, EDS_BBOX);
    end;
    GUI_ID_DEBUG_NORMALS: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      RevertDebugDataElement(Model, EDS_NORMALS);
    end;
    GUI_ID_DEBUG_SKELETON: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      RevertDebugDataElement(Model, EDS_SKELETON);
    end;
    GUI_ID_DEBUG_WIRE_OVERLAY: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      RevertDebugDataElement(Model, EDS_MESH_WIRE_OVERLAY);
    end;
    GUI_ID_DEBUG_HALF_TRANSPARENT: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      RevertDebugDataElement(Model, EDS_HALF_TRANSPARENCY);
    end;
    GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem, not Menu.IsItemChecked(Menu.GetSelectedItem));
      RevertDebugDataElement(Model, EDS_BBOX_BUFFERS);
    end;
    GUI_ID_DEBUG_ALL: // View -> Debug Information
    begin
      Menu.SetItemChecked(Menu.GetSelectedItem - 1, True);
      Menu.SetItemChecked(Menu.GetSelectedItem - 2, True);
      Menu.SetItemChecked(Menu.GetSelectedItem - 3, True);
      Menu.SetItemChecked(Menu.GetSelectedItem - 4, True);
      Menu.SetItemChecked(Menu.GetSelectedItem - 5, True);
      Menu.SetItemChecked(Menu.GetSelectedItem - 6, True);
      if Assigned(Model) then
        Model.DebugData := EDS_FULL;
    end;
    GUI_ID_ABOUT: // Help->About
      ShowAboutText;
    GUI_ID_MODEL_MATERIAL_SOLID: // View -> Material -> Solid
      if Assigned(Model) then
        Model.SetMaterialType(EMT_SOLID);
    GUI_ID_MODEL_MATERIAL_TRANSPARENT: // View -> Material -> Transparent
      if Assigned(Model) then
        Model.SetMaterialType(EMT_TRANSPARENT_ADD_COLOR);
    GUI_ID_MODEL_MATERIAL_REFLECTION: // View -> Material -> Reflection
      if Assigned(Model) then
        Model.SetMaterialType(EMT_SPHERE_MAP);
    GUI_ID_CAMERA_MAYA:
      SetActiveCamera(Camera[0]);
    GUI_ID_CAMERA_FIRST_PERSON:
      SetActiveCamera(Camera[1]);
  end;
end;

(*
  Update the display of the model scaling
*)
procedure UpdateScaleInfo(Model: TSceneNode);
var
  ToolBoxWnd: TGUIElement;
  Scale: TVector3DF;
begin
  ToolBoxWnd := Device.GetGUIEnvironment.GetRootGUIElement.GetElementFromID(GUI_ID_DIALOG_ROOT_WINDOW, True);
  if not Assigned(ToolBoxWnd) then
    Exit;

  if not Assigned(Model) then
  begin
    ToolBoxWnd.GetElementFromID(GUI_ID_X_SCALE, True).SetText('_');
    ToolBoxWnd.GetElementFromID(GUI_ID_Y_SCALE, True).SetText('_');
    ToolBoxWnd.GetElementFromID(GUI_ID_Z_SCALE, True).SetText('_');
  end
  else
  begin
    Scale := Model.GetScale^;

    ToolBoxWnd.GetElementFromID(GUI_ID_X_SCALE, True).SetText(PWideChar(SingleToStr(Scale.X)));
    ToolBoxWnd.GetElementFromID(GUI_ID_Y_SCALE, True).SetText(PWideChar(SingleToStr(Scale.Y)));
    ToolBoxWnd.GetElementFromID(GUI_ID_Z_SCALE, True).SetText(PWideChar(SingleToStr(Scale.Z)));
  end;
end;

(*
Function loadModel() loads a model and displays it using an
addAnimatedMeshSceneNode and the scene manager. Nothing difficult. It also
displays a short message box, if the model could not be loaded.
*)
procedure LoadModel(fn: String);
var
  Extension: String;
  AnsiFN: AnsiString;
  Texture: TTexture;
  j: Integer;
begin
	// modify the name if it a .pk3 file

  Extension := ExtractFileExt(fn);
  Extension := LowerCase(Extension);

  // if a texture is loaded apply it to the current model..
  if (Extension = '.jpg') or (Extension = '.pcx') or
    (Extension = '.png') or (Extension = '.ppm') or
    (Extension = '.pgm') or (Extension = '.pbm') or
    (Extension = '.psd') or (Extension = '.tga') or
    (Extension = '.bmp') or (Extension = '.wal') or
    (Extension = '.rgb') or (Extension = '.rgba') then
  begin
    Texture := Device.GetVideoDriver.GetTexture(fn);
    if Assigned(Texture) and Assigned(Model) then
    begin
      // always reload texture
      Device.GetVideoDriver.RemoveTexture(Texture);
      Texture := Device.GetVideoDriver.GetTexture(fn);

      Model.SetMaterialTexture(0, Texture);
    end;
    Exit;

  end
  // if a archive is loaded add it to the FileArchive..
  else if (Extension = '.pk3') or (Extension = '.zip') or (Extension = '.pak') or (Extension = '.npk') then
  begin
    Device.GetFileSystem.AddFileArchive(fn);
    Exit;

  end;

  // load a model into the engine
  if Assigned(Model) then
    Model.Remove;

  Model := nil;

  if (Extension = '.pk3') then
  begin
    AnsiFN := AnsiString(fn);
    Device.GetSceneManager.LoadScene(PAnsiChar(AnsiFN));

    Device.GetSceneManager.GetSceneNodesFromType( ESNT_ANIMATED_MESH, DataList );

    if DataList.GetCount > 0 then
      Model := TSceneNode(DataList[0]);
  end;

  m := Device.GetSceneManager.GetMesh(fn);
  if not Assigned(m) then
  begin
    // model could not be loaded

    if StrComp(PWideChar(StartUpModelFile), PWideChar(fn)) <> 0 then
      Device.GetGUIEnvironment.AddMessageBox
      (
        PWideChar(Caption),
        'The model could not be loaded. Maybe it is not a supported file format.'
      );

    Exit;
  end;

  // set default material properties

  if Octree then
  begin
    Model := Device.GetSceneManager.AddOctreeSceneNode(m.GetMesh(0));
  end
  else
  begin
    AnimModel := Device.GetSceneManager.AddAnimatedMeshSceneNode(m);
    AnimModel.SetAnimationSpeed(30);
    Model := AnimModel;
  end;
  Model.SetMaterialFlag(EMF_LIGHTING, UseLight);
  Model.SetMaterialFlag(EMF_NORMALIZE_NORMALS, UseLight);
  //Model.SetMaterialFlag(EMF_BACK_FACE_CULLING, False);
  Model.SetDebugDataVisible(EDS_OFF);

	// we need to uncheck the menu entries. would be cool to fake a menu event, but
	// that's not so simple. so we do it brute force
  Menu := TGUIContextMenu(Device.GetGUIEnvironment.GetRootGUIElement.GetElementFromID(GUI_ID_TOGGLE_DEBUG_INFO, True));
  if Assigned(Menu) then
  begin
    for j := 1 to 5 do
    begin
      Menu.SetItemChecked(j, False);
    end;
    UpdateScaleInfo(Model);
  end;
end;

procedure SetSkinTransparency(Alpha: S32; Skin: TGUISkin);
var
  i: EGUI_DEFAULT_COLOR;
  col: TIrrColor;
begin
  for i := Low(i) to High(i) do
  begin
    col := Skin.GetColor(i)^;
    col.SAlpha := Alpha;
    Skin.SetColor(i, col);
  end;
end;

(*
  Handle the event that one of the texture-filters was selected in the corresponding combobox.
*)
procedure OnTextureFilterSelected(Combo: TGUIComboBox);
var
  vpos: S32;
begin
  vpos := Combo.GetSelected;
  case vpos of
    0:
      if Assigned(Model) then
      begin
        Model.SetMaterialFlag(EMF_BILINEAR_FILTER, False);
        Model.SetMaterialFlag(EMF_TRILINEAR_FILTER, False);
        Model.SetMaterialFlag(EMF_ANISOTROPIC_FILTER, False);
      end;
    1:
      if Assigned(Model) then
      begin
        Model.SetMaterialFlag(EMF_BILINEAR_FILTER, True);
        Model.SetMaterialFlag(EMF_TRILINEAR_FILTER, False);
      end;
    2:
      if Assigned(Model) then
      begin
        Model.SetMaterialFlag(EMF_BILINEAR_FILTER, False);
        Model.SetMaterialFlag(EMF_TRILINEAR_FILTER, True);
      end;
    3:
      if Assigned(Model) then
      begin
        Model.SetMaterialFlag(EMF_ANISOTROPIC_FILTER, True);
      end;
    4:
      if Assigned(Model) then
      begin
        Model.SetMaterialFlag(EMF_ANISOTROPIC_FILTER, False);
      end;
  end;
end;

(*
Function createToolBox() creates a toolbox window. In this simple mesh
viewer, this toolbox only contains a tab control with three edit boxes for
changing the scale of the displayed model.
*)
procedure CreateToolBox;
var
  Env: TGUIEnvironment;
  Root, E: TGUIElement;
  Wnd: TGUIWindow;
  Tab: TGUITabControl;
  t1: TGUITab;
  ScrollBar: TGUIScrollBar;
begin
  // remove tool box if already there
  Env := Device.GetGUIEnvironment;
  Root := Env.GetRootGUIElement;
  E := Root.GetElementFromID(GUI_ID_DIALOG_ROOT_WINDOW, True);
  if Assigned(E) then
    E.Remove;

  // create the toolbox window
  Wnd := Env.AddWindow(IrrRect(600, 45, 800, 480), False, 'Toolset',
    nil, GUI_ID_DIALOG_ROOT_WINDOW);

  // create tab control and tabs
  Tab := Env.AddTabControl(IrrRect(2, 20, 800 - 602, 480 - 7), Wnd, True, True);
  Tab.SetActiveTab(0);

  t1 := Tab.AddTab('Config');

  // add some edit boxes and a button to tab one
  Env.AddStaticText('Scale:', IrrRect(10, 20, 60, 45), False, False, t1);
  Env.AddStaticText('X:', IrrRect(22, 48, 40, 66), False, False, t1);
  Env.AddEditBox('1.0', IrrRect(40, 46, 130, 66), True, t1, GUI_ID_X_SCALE);
  Env.AddStaticText('Y:', IrrRect(22, 82, 40, 96), False, False, t1);
  Env.AddEditBox('1.0', IrrRect(40, 76, 130, 96), True, t1, GUI_ID_Y_SCALE);
  Env.AddStaticText('Z:', IrrRect(22, 108, 40, 126), False, False, t1);
  Env.AddEditBox('1.0', IrrRect(40, 106, 130, 126), True, t1, GUI_ID_Z_SCALE);

  Env.AddButton(IrrRect(10, 134, 85, 165), t1, GUI_ID_BUTTON_SET_SCALE, 'Set');

  // quick scale buttons
  Env.AddButton(IrrRect(65, 20, 95, 40), t1, GUI_ID_BUTTON_SCALE_MUL10, '* 10');
  Env.AddButton(IrrRect(100, 20, 130, 40), t1, GUI_ID_BUTTON_SCALE_DIV10, '* 0.1');

  UpdateScaleInfo(Model);

  // add transparency control
  Env.AddStaticText('GUI Transparency Control:', IrrRect(10, 200, 150, 225), True, False, t1);
  ScrollBar := Env.AddScrollBar(True, IrrRect(10, 225, 150, 240), t1, GUI_ID_SKIN_TRANSPARENCY);
  ScrollBar.SetMax(255);
  ScrollBar.SetPos(255);

  // add framerate control
  Env.AddStaticText(':', IrrRect(10, 240, 150, 265), True, False, t1);
  Env.AddStaticText('Framerate:', IrrRect(12, 240, 75, 265), False, False, t1);
  // current frame info
  Env.AddStaticText('', IrrRect(75, 240, 200, 265), False, False, t1, GUI_ID_ANIMATION_INFO);

  ScrollBar := Env.AddScrollBar(True, IrrRect(10, 265, 150, 280), t1, GUI_ID_SKIN_ANIMATION_FPS);
  ScrollBar.SetMax(MAX_FRAMERATE);
  ScrollBar.SetMin(-MAX_FRAMERATE);
  ScrollBar.SetPos(DEFAULT_FRAMERATE);
  ScrollBar.SetSmallStep(1);

end;

{ TMyEventReceiver }

constructor TMyEventReceiver.Create;
begin

end;

destructor TMyEventReceiver.Destroy;
begin

  inherited;
end;

function TMyEventReceiver.OnEvent(const Event: SEvent): Boolean;
var
  id: S32;
  lenv: TGUIEnvironment;
  Dialog: TGUIFileOpenDialog;
  vpos: S32;
  root: TGUIElement;
  Scale: TVector3DF;
  s: String;
begin
  // Escape swaps Camera Input
  if (Event.EventType = EET_KEY_INPUT_EVENT) and (not Event.KeyInput.PressedDown) then
  begin
    if OnKeyUp(Event.KeyInput.Key) then
      Exit(True);
  end;

  if Event.EventType = EET_GUI_EVENT then
  begin
    id := Event.GUIEvent.Caller.GetID;
    lenv := Device.GetGUIEnvironment;

    case Event.GUIEvent.EventType of
      EGET_ELEMENT_FOCUS_LOST: ;
      EGET_ELEMENT_FOCUSED: ;
      EGET_ELEMENT_HOVERED: ;
      EGET_ELEMENT_LEFT: ;
      EGET_ELEMENT_CLOSED: ;
      EGET_BUTTON_CLICKED:
      begin
        case id of
          GUI_ID_BUTTON_SET_SCALE:
          begin
            // set scale;
            root := Env.GetRootGUIElement;
            s := root.GetElementFromID(GUI_ID_X_SCALE, True).GetText^;
            Scale.X := StrToFloatDef(s, 1.0);
            s := root.GetElementFromID(GUI_ID_Y_SCALE, True).GetText^;
            Scale.Y := StrToFloatDef(s, 1.0);
            s := root.GetElementFromID(GUI_ID_Z_SCALE, True).GetText^;
            Scale.Z := StrToFloatDef(s, 1.0);

            if Assigned(Model) then
              Model.SetScale(Scale);

            UpdateScaleInfo(Model);
          end;
          GUI_ID_BUTTON_SCALE_MUL10:
          begin
            if Assigned(Model) then
              Model.SetScale(Model.GetScale^ * 10);
            UpdateScaleInfo(Model);
          end;
          GUI_ID_BUTTON_SCALE_DIV10:
          begin
            if Assigned(Model) then
              Model.SetScale(Model.GetScale^ * 0.1);
            UpdateScaleInfo(Model);
          end;
          GUI_ID_BUTTON_OPEN_MODEL:
          begin
            Env.AddFileOpenDialog('Please select a model file to open');
          end;
          GUI_ID_BUTTON_SHOW_ABOUT:
          begin
            ShowAboutText;
          end;
          GUI_ID_BUTTON_SHOW_TOOLBOX:
          begin
            CreateToolBox;
          end;
          GUI_ID_BUTTON_SELECT_ARCHIVE:
          begin
            Env.AddFileOpenDialog('Please select your game archive/directory');
          end;
        end;
      end;
      EGET_SCROLL_BAR_CHANGED:
      begin
        // control skin transparency
        if id = GUI_ID_SKIN_TRANSPARENCY then
        begin
          vpos := TGUIScrollBar(Event.GUIEvent.Caller).GetPos;
          SetSkinTransparency(vpos, Env.GetSkin);
        end
        else if id = GUI_ID_SKIN_ANIMATION_FPS then
        begin
          vpos := TGUIScrollBar(Event.GUIEvent.Caller).GetPos;
          if Model.GetType = ESNT_ANIMATED_MESH then
            TAnimatedMeshSceneNode(Model).SetAnimationSpeed(vpos);
        end;
      end;
      EGET_CHECKBOX_CHANGED: ;
      EGET_LISTBOX_CHANGED: ;
      EGET_LISTBOX_SELECTED_AGAIN: ;
      EGET_FILE_SELECTED:
      begin
        // load the model file, selected in the file open dialog
        Dialog := TGUIFileOpenDialog(Event.GUIEvent.Caller);
        LoadModel(Dialog.GetFileName);
      end;
      EGET_DIRECTORY_SELECTED: ;
      EGET_FILE_CHOOSE_DIALOG_CANCELLED: ;
      EGET_MESSAGEBOX_YES: ;
      EGET_MESSAGEBOX_NO: ;
      EGET_MESSAGEBOX_OK: ;
      EGET_MESSAGEBOX_CANCEL: ;
      EGET_EDITBOX_ENTER: ;
      EGET_EDITBOX_CHANGED: ;
      EGET_EDITBOX_MARKING_CHANGED: ;
      EGET_TAB_CHANGED: ;
      EGET_MENU_ITEM_SELECTED:
        OnMenuItemSelected(TGUIContextMenu(Event.GUIEvent.Caller));  // a menu item was clicked
      EGET_COMBO_BOX_CHANGED:
        if id = GUI_ID_TEXTUREFILTER then    // control anti-aliasing/filtering
        begin
          OnTextureFilterSelected(TGUIComboBox(Event.GUIEvent.Caller));
        end;
      EGET_SPINBOX_CHANGED: ;
      EGET_TABLE_CHANGED: ;
      EGET_TABLE_HEADER_CHANGED: ;
      EGET_TABLE_SELECTED_AGAIN: ;
      EGET_TREEVIEW_NODE_DESELECT: ;
      EGET_TREEVIEW_NODE_SELECT: ;
      EGET_TREEVIEW_NODE_EXPAND: ;
      EGET_TREEVIEW_NODE_COLLAPSE: ;
    end;
  end;

  Result := False;
end;

(*
  Handle key-up events
*)
function TMyEventReceiver.OnKeyUp(KeyCode: EKEY_CODE): Boolean;
var
  Camera: TCameraSceneNode;
  Elem: TGUIElement;
begin
  // Don't handle keys if we have a modal dialog open as it would lead
  // to unexpected application behaviour for the user.
  if HasModalDialog then
    Exit(False);

  if KeyCode = KEY_ESCAPE then
  begin
    if Assigned(Device) then
    begin
      Camera := Device.GetSceneManager.GetActiveCamera;
      if Assigned(Camera) then
      begin
        Camera.SetInputReceiverEnabled(not Camera.IsInputReceiverEnabled);
      end;
      Exit(True);
    end;
  end
  else if KeyCode = KEY_F1 then
  begin
    if Assigned(Device) then
    begin
      Elem := Device.GetGUIEnvironment.GetRootGUIElement.GetElementFromID(GUI_ID_POSITION_TEXT);
      if Assigned(Elem) then
        Elem.SetVisible(not Elem.IsVisible);
    end;
  end
  else if KeyCode = KEY_KEY_M then
  begin
    if Assigned(Device) then
      Device.MinimizeWindow;
  end
  else if KeyCode = KEY_KEY_L then
  begin
    UseLight := not UseLight;
    if Assigned(Model) then
    begin
      Model.SetMaterialFlag(EMF_LIGHTING, UseLight);
      Model.SetMaterialFlag(EMF_NORMALIZE_NORMALS, UseLight);
    end;
  end;

  Result := False;
end;

procedure OnKillFocus;
var
  Animators: TSceneNode.TSceneNodeAnimatorList;
  Animator: TNamedObject; // TSceneNodeAnimator;
  FPSAnimator: TSceneNodeAnimatorCameraFPS;
  KeyMaps: TKeyMaps;
  i: Integer;
  Event: SEvent;
begin
	// Avoid that the FPS-camera continues moving when the user presses alt-tab while
	// moving the camera.
  Animators := Camera[1].GetAnimators^;
  for Animator in Animators do
  begin
    if TSceneNodeAnimator(Animator).GetType = ESNAT_CAMERA_FPS then
    begin
      // we send a key-down event for all keys used by this animator
      FPSAnimator := TSceneNodeAnimatorCameraFPS(Animator);
      KeyMaps := FPSAnimator.GetKeyMap;

      for i := 0 to Integer(KeyMaps.Count) - 1 do
      begin
        Event.EventType := EET_KEY_INPUT_EVENT;
        Event.KeyInput.Key := KeyMaps[i]^.KeyCode;
        Event.KeyInput.PressedDown := False;

        FPSAnimator.OnEvent(Event);
      end;
    end;
  end;
end;

procedure UpdateToolBox;
var
  lenv: TGUIEnvironment;
  lroot, ldlg: TGUIElement;
  aniInfo: TGUIStaticText;
  animatedModel: TAnimatedMeshSceneNode;
  str: String;
begin
  lenv := Device.GetGUIEnvironment;
  lroot := lenv.GetRootGUIElement;
  ldlg := lroot.GetElementFromID(GUI_ID_DIALOG_ROOT_WINDOW, True);
  if not Assigned(ldlg) then
    Exit;

  // update the info we have about the animation of the model
  aniInfo := TGUIStaticText(ldlg.GetElementFromID(GUI_ID_ANIMATION_INFO, True));
  if Assigned(aniInfo) then
  begin
    if Assigned(Model) and (Model.GetType = ESNT_ANIMATED_MESH) then
    begin
      animatedModel := TAnimatedMeshSceneNode(Model);

      str := IntToStr(Round(animatedModel.GetAnimationSpeed));
      str := str + ' Frame: ' + IntToStr(Round(animatedModel.GetFrameNr));
      aniInfo.SetText(PWideChar(str));
    end
    else
      aniInfo.SetText('');
  end;


end;

begin
  try
    DataList := TDataList.Create;

    { TODO -oUser -cConsole Main : Insert code here }
    DriverType := DriverChoiceConsole;
    if DriverType = EDT_COUNT then
    begin
      ExitCode := 1;
      Exit;
    end;

    // create device and exit if creation failed
    Receiver := TMyEventReceiver.Create;
    Device := createDevice(DriverType, [800, 600], 16, False, False, False, Receiver);
    if Device = nil then
    begin
      ExitCode := 1;   // could not create selected driver.
      Exit;
    end;

    Device.SetResizable(True);
    Device.SetWindowCaption('Irrlicht Engine - Loading...');

	  Driver := Device.GetVideoDriver;
	  Env := Device.GetGUIEnvironment;
    Smgr := Device.GetSceneManager;

    Attributes := Smgr.GetParameters;
    Attributes.SetAttribute(COLLADA_CREATE_SCENE_INSTANCES, True);

    Driver.SetTextureCreationFlag(ETCF_ALWAYS_32_BIT, True);

    Smgr.AddLightSceneNode(nil, Vector3DF(200, 200, 200), IrrColorF(1.0, 1.0, 1.0), 2000);
    Smgr.SetAmbientLight(IrrColorF(0.3, 0.3, 0.3));
    // add our media directory as "search path"
    Device.GetFileSystem.AddFileArchive('..\..\media\');

    (*
    The next step is to read the configuration file. It is stored in the xml
    format and looks a little bit like this:

    @verbatim
    <?xml version="1.0"?>
    <config>
      <startUpModel file="some filename" />
      <messageText caption="Irrlicht Engine Mesh Viewer">
        Hello!
      </messageText>
    </config>
    @endverbatim

    We need the data stored in there to be written into the global variables
    StartUpModelFile, MessageText and Caption. This is now done using the
    Irrlicht Engine integrated XML parser:
    *)

    // read configuration from xml file
    xml := Device.GetFileSystem.CreateXMLReader('config.xml');

    while Assigned(XML) and XML.Read do
    begin
      case XML.GetNodeType of
        EXN_TEXT:
    			// in this xml file, the only text which occurs is the
		    	// messageText
          MessageText := xml.GetNodeData;
        EXN_ELEMENT:
        begin
          NodeName := xml.GetNodeName;
          if StrComp(NodeName, 'startUpModel') = 0 then
          begin
            StartUpModelFile := xml.GetAttributeValue('file');
          end else if StrComp(NodeName, 'messageText') = 0 then
          begin
            Caption := xml.GetAttributeValue('caption');
          end;
        end;
      end;
    end;

    if Assigned(xml) then xml.Drop;

    if ParamCount > 1 then
      StartUpModelFile := PWideChar(ParamStr(1));

    (*
    That wasn't difficult. Now we'll set a nicer font and create the Menu.
    It is possible to create submenus for every menu item. The call
    menu->addItem(L"File", -1, true, true); for example adds a new menu
    Item with the name "File" and the id -1. The following parameter says
    that the menu item should be enabled, and the last one says, that there
    should be a submenu. The submenu can now be accessed with
    menu->getSubMenu(0), because the "File" entry is the menu item with
    index 0.
    *)

    // set a nicer font

    skin := Env.GetSkin;
    font := Env.GetFont('fonthaettenschweiler.bmp');
    if Assigned(font) then
      skin.SetFont(font);

    // create menu
    Menu := Env.AddMenu;
    Menu.AddItem('File', -1, True, True);
    Menu.AddItem('View', -1, True, True);
    Menu.AddItem('Camera', -1, True, True);
    Menu.AddItem('Help', -1, True, True);

    SubMenu := Menu.GetSubMenu(0);
    SubMenu.AddItem('Open Model File & Texture...', GUI_ID_OPEN_MODEL);
    SubMenu.AddItem('Set Model Archive...', GUI_ID_SET_MODEL_ARCHIVE);
    SubMenu.AddItem('Load as Octree', GUI_ID_LOAD_AS_OCTREE);
    SubMenu.AddSeparator;
    SubMenu.AddItem('Quit', GUI_ID_QUIT);

    SubMenu := Menu.GetSubMenu(1);
    SubMenu.AddItem('sky box visible', GUI_ID_SKY_BOX_VISIBLE, True, False, True);
    SubMenu.AddItem('toggle model debug information', GUI_ID_TOGGLE_DEBUG_INFO, True, True);
    SubMenu.AddItem('model material', -1, True, True);

    SubMenu := SubMenu.GetSubMenu(1);
    SubMenu.AddItem('Off', GUI_ID_DEBUG_OFF);
    SubMenu.AddItem('Bounding Box', GUI_ID_DEBUG_BOUNDING_BOX);
    SubMenu.AddItem('Normals', GUI_ID_DEBUG_NORMALS);
    SubMenu.AddItem('Skeleton', GUI_ID_DEBUG_SKELETON);
    SubMenu.AddItem('Wire overlay', GUI_ID_DEBUG_WIRE_OVERLAY);
    SubMenu.AddItem('Half-Transparent', GUI_ID_DEBUG_HALF_TRANSPARENT);
    SubMenu.AddItem('Buffers bounding boxes', GUI_ID_DEBUG_BUFFERS_BOUNDING_BOXES);
    SubMenu.AddItem('All', GUI_ID_DEBUG_ALL);

    SubMenu := menu.GetSubMenu(1).GetSubMenu(2);
    SubMenu.AddItem('Solid', GUI_ID_MODEL_MATERIAL_SOLID);
    SubMenu.AddItem('Transparent', GUI_ID_MODEL_MATERIAL_TRANSPARENT);
    SubMenu.AddItem('Reflection', GUI_ID_MODEL_MATERIAL_REFLECTION);

    SubMenu := menu.GetSubMenu(2);
    SubMenu.AddItem('May Style', GUI_ID_CAMERA_MAYA);
    SubMenu.AddItem('First Person', GUI_ID_CAMERA_FIRST_PERSON);

    SubMenu := menu.GetSubMenu(3);
    SubMenu.AddItem('About', GUI_ID_ABOUT);

    (*
    Below the menu we want a toolbar, onto which we can place colored
    buttons and important looking stuff like a senseless combobox.
    *)

    // create toolbar

    Bar := Env.AddToolBar;

    Image := Driver.GetTexture('open.png');
    Bar.AddButton(GUI_ID_BUTTON_OPEN_MODEL, nil, 'Open a model', Image, nil, False, True);

    Image := Driver.GetTexture('tools.png');
    Bar.AddButton(GUI_ID_BUTTON_SHOW_TOOLBOX, nil, 'Open Toolset', Image, nil, False, True);

    Image := Driver.GetTexture('zip.png');
    Bar.AddButton(GUI_ID_BUTTON_SELECT_ARCHIVE, nil, 'Set Model Archive', Image, nil, False, True);

    Image := Driver.GetTexture('help.png');
    Bar.AddButton(GUI_ID_BUTTON_SHOW_ABOUT, nil, 'Open Help', Image, nil, False, True);

    // create a combobox for texture filters

    Box := Env.AddComboBox(IrrRect(250, 4, 350, 23), Bar, GUI_ID_TEXTUREFILTER);
    Box.AddItem('No filtering');
    Box.AddItem('Bilinear');
    Box.AddItem('Trilinear');
    Box.AddItem('Anisotropic');
    Box.AddItem('Isotropic');

    (*
    To make the editor look a little bit better, we disable transparent gui
    elements, and add an Irrlicht Engine logo. In addition, a text showing
    the current frames per second value is created and the window caption is
    changed.
    *)

    // disable alpha

    for i := Low(i) to High(i) do
    begin
      Col := Env.GetSkin.GetColor(i)^;
      Col.SAlpha := 255;
      Env.GetSkin.SetColor(i, Col);
    end;

    // add a tabcontrol

    CreateToolBox;

    FPSText := Env.AddStaticText('', IrrRect(400, 4, 570, 23), True, False, Bar);
    PosText := Env.AddStaticText('', IrrRect(10, 50, 470, 80), False, False, nil, GUI_ID_POSITION_TEXT);
    PosText.SetVisible(False);

    // set window caption

    Caption := Caption + ' - [';
    Caption := Caption + Driver.GetName + ']';
    Device.SetWindowCaption(PWideChar(Caption));

    (*
    That's nearly the whole application. We simply show the about message
    box at start up, and load the first model. To make everything look
    better, a skybox is created and a user controlled camera, to make the
    application a little bit more interactive. Finally, everything is drawn
    in a standard drawing loop.
    *)

    // show about message box and load default model
    if ParamCount = 0 then
      ShowAboutText;
    LoadModel(StartUpModelFile);

    // add skybox
    SkyBox := Smgr.AddSkyBoxSceneNode
      (
        Driver.GetTexture('irrlicht2_up.jpg'),
        Driver.GetTexture('irrlicht2_dn.jpg'),
        Driver.GetTexture('irrlicht2_lf.jpg'),
        Driver.GetTexture('irrlicht2_rt.jpg'),
        Driver.GetTexture('irrlicht2_ft.jpg'),
        Driver.GetTexture('irrlicht2_bk.jpg')
      );

    // add a camera scene node
    Camera[0] := Smgr.AddCameraSceneNodeMaya;
    Camera[0].SetFarValue(20000);
  	// Maya cameras reposition themselves relative to their target, so target the location
	  // where the mesh scene node is placed.
    Camera[0].SetTarget(Vector3DF(0, 30, 0));

    Camera[1] := Smgr.AddCameraSceneNodeFPS;
    Camera[1].SetFarValue(20000);
    Camera[1].SetPosition(Vector3DF(0, 0, -70));
    Camera[1].SetTarget(Vector3DF(0, 30, 0));

    SetActiveCamera(Camera[0]);

    // load the irrlicht engine logo
    Img := Env.AddImage(Driver.GetTexture('irrlichtlogo2.png'), Vector2D(10, Driver.GetScreenSize^.Height - 128));

    // lock the logo's edges to the bottom left corner of the screen
    Img.SetAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);

    // remember state so we notice when the window does lose the focus
    hasFocus := Device.IsWindowFocused;

    while (Device.Run and Assigned(Driver)) do
    begin
      focused := Device.IsWindowFocused;
      if hasFocus and (not focused) then
        OnKillFocus;
      hasFocus := focused;

      if Device.IsWindowActive then
      begin
        Driver.BeginScene(True, True, IrrColor(50, 50, 50, 150));

        Smgr.DrawAll;
        Env.DrawAll;

        Driver.EndScene;

        // update information about current frame-rate
        str := Format('FPS: %d Tris: %d', [Driver.GetFPS, Driver.GetPrimitiveCountDrawn]);
        FPSText.SetText(PWideChar(str));

        // update information about the active camera
        Cam := Device.GetSceneManager.GetActiveCamera;
        CamPosition := Cam.GetPosition^;
        str := Format
          (
            'Pos: %0.5f %0.5f %0.5f %0.5f %0.5f %0.5f ',
            [CamPosition.X, CamPosition.Y, CamPosition.Z, CamPosition.X, CamPosition.Y, CamPosition.Z]
          );
        PosText.SetText(PWideChar(Str));


        // update the tool dialog
        UpdateToolBox;
      end
      else
        Device.Yield;
    end;

    Device.Drop;

    Receiver.Free;

    DataList.Free;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
